package com.f2prateek.rx.preferences3;


import java.util.Collections;
import java.util.Set;

import io.reactivex.rxjava3.annotations.CheckReturnValue;
import io.reactivex.rxjava3.annotations.NonNull;
import io.reactivex.rxjava3.core.Observable;
import io.reactivex.rxjava3.core.ObservableEmitter;
import io.reactivex.rxjava3.core.ObservableOnSubscribe;
import io.reactivex.rxjava3.functions.Cancellable;
import ohos.data.preferences.Preferences;

import static com.f2prateek.rx.preferences3.Preconditions.checkNotNull;

/**
 * A factory for reactive {@link Preference} objects.
 */
public final class RxSharedPreferences {
    private static final Float DEFAULT_FLOAT = 0f;
    private static final Integer DEFAULT_INTEGER = 0;
    private static final Boolean DEFAULT_BOOLEAN = false;
    private static final Long DEFAULT_LONG = 0L;
    private static final String DEFAULT_STRING = "";

    /**
     * Create an instance of {@link RxSharedPreferences} for {@code preferences}.
     *
     * @param preferences
     * @return RxSharedPreferences
     */
    @CheckReturnValue
    @NonNull
    public static RxSharedPreferences create(@NonNull Preferences preferences) {
        checkNotNull(preferences, "preferences == null");
        return new RxSharedPreferences(preferences);
    }

    private final Preferences preferences;
    private final Observable<String> keyChanges;

    private RxSharedPreferences(final Preferences preferences) {
        this.preferences = preferences;
        this.keyChanges = Observable.create(new ObservableOnSubscribe<String>() {
            @Override
            public void subscribe(final ObservableEmitter<String> emitter) {
                final Preferences.PreferencesObserver listener = new Preferences.PreferencesObserver() {
                    @Override
                    public void onChange(Preferences preferences, String key) {
                        emitter.onNext(key);
                    }

                };

                emitter.setCancellable(new Cancellable() {
                    @Override
                    public void cancel() {
                        preferences.unregisterObserver(listener);
                    }
                });

                preferences.registerObserver(listener);
            }
        }).share();
    }

    /**
     * Create a boolean preference for {@code key}. Default is {@code false}.
     *
     * @param key
     * @return Preference
     */
    @CheckReturnValue
    @NonNull
    public Preference<Boolean> getBoolean(@NonNull String key) {
        return getBoolean(key, DEFAULT_BOOLEAN);
    }

    /**
     * Create a boolean preference for {@code key} with a default of {@code defaultValue}.
     *
     * @param key
     * @param defaultValue
     * @return Preference
     */
    @CheckReturnValue
    @NonNull
    public Preference<Boolean> getBoolean(@NonNull String key, @NonNull Boolean defaultValue) {
        checkNotNull(key, "key == null");
        checkNotNull(defaultValue, "defaultValue == null");
        return new RealPreference<>(preferences, key, defaultValue, BooleanAdapter.INSTANCE, keyChanges);
    }

    /**
     * Create an enum preference for {@code key} with a default of {@code defaultValue}.
     *
     * @param defaultValue
     * @param key
     * @param enumClass
     * @return Preference
     */
    @CheckReturnValue
    @NonNull
    public <T extends Enum<T>> Preference<T> getEnum(@NonNull String key, @NonNull T defaultValue,
                                                     @NonNull Class<T> enumClass) {
        checkNotNull(key, "key == null");
        checkNotNull(defaultValue, "defaultValue == null");
        checkNotNull(enumClass, "enumClass == null");
        return new RealPreference<>(preferences, key, defaultValue, new EnumAdapter<>(enumClass), keyChanges);
    }

    /**
     * Create a float preference for {@code key}. Default is {@code 0}.
     *
     * @param key
     * @return Preference
     */
    @CheckReturnValue
    @NonNull
    public Preference<Float> getFloat(@NonNull String key) {
        return getFloat(key, DEFAULT_FLOAT);
    }

    /**
     * Create a float preference for {@code key} with a default of {@code defaultValue}.
     *
     * @param key
     * @param defaultValue
     * @return Preference
     */
    @CheckReturnValue
    @NonNull
    public Preference<Float> getFloat(@NonNull String key, @NonNull Float defaultValue) {
        checkNotNull(key, "key == null");
        checkNotNull(defaultValue, "defaultValue == null");
        return new RealPreference<>(preferences, key, defaultValue, FloatAdapter.INSTANCE, keyChanges);
    }

    /**
     * Create an integer preference for {@code key}. Default is {@code 0}.
     *
     * @param key
     * @return Preference
     */
    @CheckReturnValue
    @NonNull
    public Preference<Integer> getInteger(@NonNull String key) {
        return getInteger(key, DEFAULT_INTEGER);
    }

    /**
     * Create an integer preference for {@code key} with a default of {@code defaultValue}.
     *
     * @param key
     * @param defaultValue
     * @return Preference
     */
    @CheckReturnValue
    @NonNull
    public Preference<Integer> getInteger(@NonNull String key, @NonNull Integer defaultValue) {
        checkNotNull(key, "key == null");
        checkNotNull(defaultValue, "defaultValue == null");
        return new RealPreference<>(preferences, key, defaultValue, IntegerAdapter.INSTANCE, keyChanges);
    }

    /**
     * Create a long preference for {@code key}. Default is {@code 0}.
     *
     * @param key
     * @return Preference
     */
    @CheckReturnValue
    @NonNull
    public Preference<Long> getLong(@NonNull String key) {
        return getLong(key, DEFAULT_LONG);
    }

    /**
     * Create a long preference for {@code key} with a default of {@code defaultValue}.
     *
     * @param key
     * @param defaultValue
     * @return Preference
     */
    @CheckReturnValue
    @NonNull
    public Preference<Long> getLong(@NonNull String key, @NonNull Long defaultValue) {
        checkNotNull(key, "key == null");
        checkNotNull(defaultValue, "defaultValue == null");
        return new RealPreference<>(preferences, key, defaultValue, LongAdapter.INSTANCE, keyChanges);
    }

    /**
     * Create a preference for type {@code T} for {@code key} with a default of {@code defaultValue}.
     *
     * @param defaultValue
     * @param key
     * @param converter
     * @return Preference
     */
    @CheckReturnValue
    @NonNull
    public <T> Preference<T> getObject(@NonNull String key, @NonNull T defaultValue,
                                       @NonNull Preference.Converter<T> converter) {
        checkNotNull(key, "key == null");
        checkNotNull(defaultValue, "defaultValue == null");
        checkNotNull(converter, "converter == null");
        return new RealPreference<>(preferences, key, defaultValue,
                new ConverterAdapter<>(converter), keyChanges);
    }

    /**
     * Create a string preference for {@code key}. Default is {@code ""}.
     *
     * @param key
     * @return Preference
     */
    @CheckReturnValue
    @NonNull
    public Preference<String> getString(@NonNull String key) {
        return getString(key, DEFAULT_STRING);
    }

    /**
     * Create a string preference for {@code key} with a default of {@code defaultValue}.
     *
     * @param key
     * @param defaultValue
     * @return Preference
     */
    @CheckReturnValue
    @NonNull
    public Preference<String> getString(@NonNull String key, @NonNull String defaultValue) {
        checkNotNull(key, "key == null");
        checkNotNull(defaultValue, "defaultValue == null");
        return new RealPreference<>(preferences, key, defaultValue, StringAdapter.INSTANCE, keyChanges);
    }

    /**
     * Create a string set preference for {@code key}. Default is an empty set. Note that returned set
     * value will always be unmodifiable.
     *
     * @param key
     * @return Preference
     */
    @CheckReturnValue
    @NonNull
    public Preference<Set<String>> getStringSet(@NonNull String key) {
        return getStringSet(key, Collections.<String>emptySet());
    }

    /**
     * Create a string set preference for {@code key} with a default of {@code defaultValue}.
     *
     * @param key
     * @param defaultValue
     * @return Preference
     */
    @CheckReturnValue
    @NonNull
    public Preference<Set<String>> getStringSet(@NonNull String key,
                                                @NonNull Set<String> defaultValue) {
        checkNotNull(key, "key == null");
        checkNotNull(defaultValue, "defaultValue == null");
        return new RealPreference<>(preferences, key, defaultValue, StringSetAdapter.INSTANCE, keyChanges);
    }

    /**
     * clear the preferences
     */
    public void clear() {
        preferences.clear().flush();
    }
}
